/***************************************************************
 * Name:      Structures.cpp
 * Purpose:   Defines the code of the Structures used in GanitSheets
 * Author:    Milind Gupta (milind.gupta@gmail.com)
 * Website:   https://code.google.com/p/ganitsheets/
 * Created:   2011-03-02
 * Copyright: Milind Gupta ()
 * License:   Apache License 2.0
 **************************************************************/

#include "Structures.h"

class idCard *idList::getIDbyName(std::string idName)
{
    listManager<idCard>::structListItem *tempCardList;
    tempCardList = this->firstStructList;
    while(tempCardList)
    {
        if(tempCardList->currItem->idName == idName)
        {
            return tempCardList->currItem;
            break;
        }
        tempCardList = tempCardList->nextStruct;
    }
    return 0;
}

class idCard *idList::getIDbyVal(int idVal)
{
    listManager<idCard>::structListItem *tempCardList;
    tempCardList = this->firstStructList;
    while(tempCardList)
    {
        if(tempCardList->currItem->idVal == idVal)
        {
            return tempCardList->currItem;
            break;
        }
        tempCardList = tempCardList->nextStruct;
    }
    return 0;
}

// To create a new ID object in the idList
class idCard *idList::AddID(std::string idName, int idVal)
{
    class idCard *newIDCard;
    newIDCard = this->AddItem();
    newIDCard->idName = idName;
    newIDCard->idVal = idVal;
    return newIDCard;
}

idCard::idCard(std::string Name="", int Value=wxNewId())
{
    this->idName = Name;
    this->idVal = Value;
}

spreadSheet::cell *spreadSheet::getCell(unsigned int row, unsigned int column)
{
    shtSize sizeOfSheet;
    sizeOfSheet = this->sheetSize();
    if(row>sizeOfSheet.rows)
        row = sizeOfSheet.rows;
    if(column>sizeOfSheet.columns)
        column = sizeOfSheet.columns;
    spreadSheet::cell *newCell;
    newCell = new spreadSheet::cell(this,row,column);
    return newCell;
}

spreadSheet::cell::cell(spreadSheet *parent, unsigned int row, unsigned int column)
{
    this->Parent = parent;
    this->Row = row;
    this->Column = column;
}

// Function to return the row to which the cell refers
unsigned int spreadSheet::cell::row()
{
    return this->Row;
}

// Function to return the column to which the cell refers
unsigned int spreadSheet::cell::column()
{
    return this->Column;
}

// Function to return the value in the spreadsheet cell
// The function checks whether the cell exists in the memory if not then returns null value
// If yes then returns the cell value
std::string spreadSheet::cell::value()
{
    spreadSheet::cellStruct *tempCell;
    tempCell = this->Parent->Cells->getCell(this->Row, this->Column);
    if(!tempCell)
        return "";
    else
        return tempCell->value;
}

// Function set the value in a spreadsheet cell
// The function checks whether the cell exists in the memory if not then creates the cell
// and sets the value otherwise simply sets the value
bool spreadSheet::cell::setValue(std::string value)
{
    spreadSheet::cellStruct *tempCell;
    tempCell = this->Parent->Cells->getCell(this->Row,this->Column);
    if(!tempCell)
    {
        if(value!="")
            tempCell = this->Parent->Cells->addCell(this->Row, this->Column);
        else
            return true;
        if(!tempCell)
            return false;
    }
    tempCell->value = value;
    if(tempCell->isEmpty())
        this->Parent->Cells->removeCell(this->Row, this->Column);
    else
    {
        // Set the value in the GUI
        this->Parent->sheetGrid()->SetCellValue(this->Row-1,this->Column-1, \
                wxString(value.c_str(),wxConvUTF8));
        // Start the evaluation here
    }
    return true;
}

// Function to return the last evaluation result of the cell
// The function first checks if the cell exists in memory if not then returns the default value
// otherwise returns the last evaluation value stored in the cell
std::string spreadSheet::cell::lastEval()
{
    spreadSheet::cellStruct *tempCell;
    tempCell = this->Parent->Cells->getCell(this->Row, this->Column);
    if(!tempCell)
        return "";
    else
        return tempCell->lastEval;
}

// Function to return the parent sheet of the cell
spreadSheet *spreadSheet::cell::parent()
{
    return this->Parent;
}

// Function to return the next cell in the same row (the next column cell)
// Function simply creates a new cell object with the row and column attributes properly set
spreadSheet::cell *spreadSheet::cell::nextCellInColumn()
{
    if(this->Column+1 > this->Parent->sheetSize().columns)
        return this;
    spreadSheet::cell *nextCell;
    nextCell = new spreadSheet::cell(this->Parent, this->Row, this->Column + 1);
    return nextCell;
}

// Function to return the next cell in the same column (the next row cell)
// Function simply creates a new cell object with the row and column attributes properly set
spreadSheet::cell *spreadSheet::cell::nextCellInRow()
{
    if(this->Row+1 > this->Parent->sheetSize().rows)
        return this;
    spreadSheet::cell *nextCell;
    nextCell = new spreadSheet::cell(this->Parent, this->Row + 1, this->Column);
    return nextCell;
}

// Function to return the previous cell in the same row (the previous column cell)
// Function simply creates a new cell object with the row and column attributes properly set
spreadSheet::cell *spreadSheet::cell::prevCellInColumn()
{
    if(this->Column == 1)
        return this;
    spreadSheet::cell *nextCell;
    nextCell = new spreadSheet::cell(this->Parent, this->Row, this->Column - 1);
    return nextCell;
}

// Function to return the previous cell in the same column (the previous row cell)
// Function simply creates a new cell object with the row and column attributes properly set
spreadSheet::cell *spreadSheet::cell::prevCellInRow()
{
    if(this->Row == 1)
        return this;
    spreadSheet::cell *nextCell;
    nextCell = new spreadSheet::cell(this->Parent, this->Row - 1, this->Column);
    return nextCell;
}

spreadSheet::cellStruct::cellStruct(spreadSheet *parent, unsigned int row, unsigned int column)
{
    this->Column = column;
    this->Row = row;
    this->fastValue = "";
    this->lastEval = "";
    this->NextCellInColumn = 0;
    this->PrevCellInColumn = 0;
    this->NextCellInRow = 0;
    this->PrevCellInRow = 0;
    this->parentSheet = parent;
    this->value = "";
}

// Function to check whether a cell data structure stored in memory to represent a cell is empty
bool spreadSheet::cellStruct::isEmpty()
{
    // Later add formatting empty checks also
    if(this->value == "")
        return true;
    else
        return false;
}

unsigned int spreadSheet::cellStruct::row()
{
    // Return the row
    return this->Row;
}

unsigned int spreadSheet::cellStruct::column()
{
    // Return the column
    return this->Column;
}

spreadSheet::cellStruct *spreadSheet::cellStruct::nextCellInColumn()
{
    return this->NextCellInColumn;
}

spreadSheet::cellStruct *spreadSheet::cellStruct::prevCellInColumn()
{
    return this->PrevCellInColumn;
}

spreadSheet::cellStruct *spreadSheet::cellStruct::nextCellInRow()
{
    return this->NextCellInRow;
}

spreadSheet::cellStruct *spreadSheet::cellStruct::prevCellInRow()
{
    return this->PrevCellInRow;
}

spreadSheet *spreadSheet::cellStruct::parent()
{
    return this->parentSheet;
}

// Constructor
spreadSheet::cellsStruct::cellsStruct(spreadSheet *parent)
{
    this->count = 0;
    this->parent = parent;
    this->rowFirstsIndex1 = 0;
    this->columnFirstsIndex1 = 0;
}

class spreadSheet::cellStruct *spreadSheet::cellsStruct::firstCellInColumn(unsigned int column)
{
    if(!this->columnFirstsIndex1)
        return 0;   // No cells
    spreadSheet::cellsStruct::firstCellList *currFirstCell;
    unsigned int count;
    currFirstCell = this->columnFirstsIndex1;

    count = currFirstCell->firstCell->column();
    while(count<column)
    {
        if(!currFirstCell->nextIndex)
            return 0;   // No cell at row
        currFirstCell = currFirstCell->nextIndex;
        count = currFirstCell->firstCell->column();
    }
    if(count!=column)
        return 0;
    return currFirstCell->firstCell;
}

spreadSheet::cellStruct *spreadSheet::cellsStruct::firstCellInRow(unsigned int row)
{
    if(!this->rowFirstsIndex1)
        return 0;   // No cells
    spreadSheet::cellsStruct::firstCellList *currFirstCell;
    unsigned int count;
    currFirstCell = this->rowFirstsIndex1;

    count = currFirstCell->firstCell->row();
    while(count<row)
    {
        if(!currFirstCell->nextIndex)
            return 0;   // No cell at row
        currFirstCell = currFirstCell->nextIndex;
        count = currFirstCell->firstCell->row();
    }
    if(count!=row)
        return 0;
    return currFirstCell->firstCell;
}

spreadSheet::cellStruct *spreadSheet::cellsStruct::getCell(unsigned int row, unsigned int column)
{
    if(!this->rowFirstsIndex1)
        return 0;
    spreadSheet::cellsStruct::firstCellList *currFirstCell;
    spreadSheet::cellStruct *tempCell;
    unsigned int count;
    currFirstCell = this->rowFirstsIndex1;
    // Find the first cell in the row
    count = currFirstCell->firstCell->row();
    while(count<row)
    {
        if(!currFirstCell->nextIndex)
            // No cell in row
            return 0;
        currFirstCell = currFirstCell->nextIndex;
        count = currFirstCell->firstCell->row();
    }
    if(count!=row)
        return 0;   // No cell in current row
    // Find the cell with the same column
    tempCell = currFirstCell->firstCell;
    while(tempCell->Column<column)
    {
        if(!tempCell->NextCellInColumn)
            return 0;   // No cell at row,column
        tempCell = tempCell->NextCellInColumn;
    }
    if(tempCell->Column!=column)
        return 0;   // No cell at row,column
    return tempCell;
}

spreadSheet::cellsStruct::firstCellList::firstCellList()
{
    this->firstCell = 0;
    this->prevIndex = 0;
    this->nextIndex = 0;
}

bool spreadSheet::cellsStruct::removeCell(unsigned int row, unsigned int column)
{
    spreadSheet::cellStruct *tempCell;
    spreadSheet::cellsStruct::firstCellList *currFirstCell;
    unsigned int count;
    tempCell = this->getCell(row, column);
    if(!tempCell)
        return false;   // Cell not found
    // Find if the cell is placed in the rowFirst linked lists
    currFirstCell = this->rowFirstsIndex1;
    count = currFirstCell->firstCell->row();
    while(count<row)
    {
        if(!currFirstCell->nextIndex)
            break;
        currFirstCell = currFirstCell->nextIndex;
        count = currFirstCell->firstCell->row();
    }
    if(count==row)
    {
        // Check if it is the same cell
        if(currFirstCell->firstCell->column()==column)
        {
            // This is the same cell
            // Check if there is a next cell in the same row
            if(currFirstCell->firstCell->nextCellInRow())
                // There is a cell in the same row so replace that in the first cells
                currFirstCell->firstCell = currFirstCell->firstCell->nextCellInRow();
            else
            {
                // No cells remaining in the current row
                // Link the rowFirst linked list across currFirstCell
                if(currFirstCell->nextIndex)
                    currFirstCell->nextIndex->prevIndex = currFirstCell->prevIndex;
                currFirstCell->prevIndex->nextIndex = currFirstCell->nextIndex;
                delete currFirstCell;
            }
        }
    }   // if(count==row) ends
    // Find if the cell is placed in the columnFirst linked lists
    currFirstCell = this->columnFirstsIndex1;
    count = currFirstCell->firstCell->column();
    while(count<count)
    {
        if(!currFirstCell->nextIndex)
            break;
        currFirstCell = currFirstCell->nextIndex;
        count = currFirstCell->firstCell->column();
    }
    if(count==column)
    {
        // Check if it is the same cell
        if(currFirstCell->firstCell->row()==row)
        {
            // This is the same cell
            // Check if there is a next cell in the same row
            if(currFirstCell->firstCell->nextCellInColumn())
                // There is a cell in the same row so replace that in the first cells
                currFirstCell->firstCell = currFirstCell->firstCell->nextCellInColumn();
            else
            {
                // No cells remaining in the current row
                // Link the rowFirst linked list across currFirstCell
                if(currFirstCell->nextIndex)
                    currFirstCell->nextIndex->prevIndex = currFirstCell->prevIndex;
                currFirstCell->prevIndex->nextIndex = currFirstCell->nextIndex;
                delete currFirstCell;
            }
        }
    }   // if(count==row) ends
    // Now delete the actual cellStrunct
    if(tempCell->PrevCellInColumn)
    {
        if(tempCell->NextCellInColumn)
        {
            // Cells exist both top and bottom to it
            tempCell->PrevCellInColumn->NextCellInColumn = tempCell->NextCellInColumn;
            tempCell->NextCellInColumn->PrevCellInColumn = tempCell->PrevCellInColumn;
        }
        else
            // Only PrevCellInColumn exists
            tempCell->PrevCellInColumn->NextCellInColumn = 0;
    }
    else if(tempCell->NextCellInColumn)
        // Only NextCellInColumn exists
        tempCell->NextCellInColumn->PrevCellInColumn = 0;
    if(tempCell->PrevCellInRow)
    {
        if(tempCell->NextCellInRow)
        {
            // Cells exist both before and after to it in the row
            tempCell->PrevCellInRow->NextCellInRow = tempCell->NextCellInRow;
            tempCell->NextCellInRow->PrevCellInRow = tempCell->PrevCellInRow;
        }
        else
            // Only PrevCellInRow exists
            tempCell->PrevCellInRow->NextCellInRow = 0;
    }
    else if(tempCell->NextCellInRow)
        // Only NextCellInRow exists
        tempCell->NextCellInRow->PrevCellInRow = 0;
    delete tempCell;
    return true;
}

spreadSheet::cellStruct *spreadSheet::cellsStruct::addCell(unsigned int row, unsigned int column)
{
    spreadSheet::cellStruct *tempCell, *newCell;
    spreadSheet::cellsStruct::firstCellList *currFirstCell, *newFirstCell;
    unsigned int count;
    tempCell = this->getCell(row,column);
    if(tempCell)
        return tempCell;       // Cell already exists
    newCell = new spreadSheet::cellStruct(this->parent,row,column); // Create an empty cell
    this->count = this->count + 1;  // Add to the total number of cells
    // Now place the cell in the data structure 2D linked list and the firsts list if applicable
    if(!this->rowFirstsIndex1)
    {
        // No cells exist so create the rowFirsts and columnFirsts linked lists to store the firsts
        this->rowFirstsIndex1 = new spreadSheet::cellsStruct::firstCellList;
        this->columnFirstsIndex1 = new spreadSheet::cellsStruct::firstCellList;
        this->rowFirstsIndex1->firstCell = newCell;
        this->columnFirstsIndex1->firstCell = newCell;
    }
    else
    {
        // Find appropriate place to put newCell
        currFirstCell = this->rowFirstsIndex1;
        // Search for a cell in the same row
        count = currFirstCell->firstCell->row();
        while(count<row)
        {
            if(!currFirstCell->nextIndex)
                break;
            currFirstCell = currFirstCell->nextIndex;
            count = currFirstCell->firstCell->row();
        }
        if(count==row)
        {
            // Check if this newCell needs to be placed here
            if(currFirstCell->firstCell->column()>column)
            {
                newCell->NextCellInRow = currFirstCell->firstCell;
                currFirstCell->firstCell->PrevCellInRow = newCell;
                currFirstCell->firstCell = newCell;
            }
            else
            {
                tempCell = currFirstCell->firstCell;
                while(tempCell->Column<column)
                {
                    if(!tempCell->NextCellInRow)
                        break;
                    tempCell = tempCell->NextCellInRow;
                }
                if(tempCell->Column<column)
                {
                    tempCell->NextCellInRow = newCell;
                    newCell->PrevCellInRow = tempCell;
                }
                else
                {
                    // tempCell->Column>column
                    newCell->PrevCellInRow = tempCell->PrevCellInRow;
                    newCell->NextCellInRow = tempCell;
                    newCell->PrevCellInRow->NextCellInRow = newCell;
                    newCell->NextCellInRow->PrevCellInRow = newCell;
                }
            }   // if(currFirstCell->firstCell->column()>column) ends
        }   // if(count==row) ends
        else if (count<row)
        {
            currFirstCell->nextIndex = new spreadSheet::cellsStruct::firstCellList;
            currFirstCell->nextIndex->prevIndex = currFirstCell;
            currFirstCell->nextIndex->firstCell = newCell;
        }
        else
        {
            // count > row
            newFirstCell = new spreadSheet::cellsStruct::firstCellList;
            newFirstCell->firstCell = newCell;
            if(currFirstCell->prevIndex)
            {
                newFirstCell->prevIndex = currFirstCell->prevIndex;
                newFirstCell->prevIndex->nextIndex = newFirstCell;
            }
            newFirstCell->nextIndex = currFirstCell;
            currFirstCell->prevIndex = newFirstCell;
        }   // if(count==row) ends
        // Search for the cell in the same column
        currFirstCell = this->columnFirstsIndex1;
        count = currFirstCell->firstCell->column();
        while(count<column)
        {
            if(!currFirstCell->nextIndex)
                break;
            currFirstCell = currFirstCell->nextIndex;
            count = currFirstCell->firstCell->column();
        }
        if(count==column)
        {
            // Check if this newCell needs to be placed here
            if(currFirstCell->firstCell->row()>row)
            {
                newCell->NextCellInColumn = currFirstCell->firstCell;
                currFirstCell->firstCell->PrevCellInColumn = newCell;
                currFirstCell->firstCell = newCell;
            }
            else
            {
                tempCell = currFirstCell->firstCell;
                while(tempCell->Row<row)
                {
                    if(!tempCell->NextCellInColumn)
                        break;
                    tempCell = tempCell->NextCellInColumn;
                }
                if(tempCell->Row<row)
                {
                    tempCell->NextCellInColumn = newCell;
                    newCell->PrevCellInColumn = tempCell;
                }
                else
                {
                    // tempCell->Row>row
                    newCell->PrevCellInColumn = tempCell->PrevCellInColumn;
                    newCell->NextCellInColumn = tempCell;
                    newCell->PrevCellInColumn->NextCellInColumn = newCell;
                    newCell->NextCellInColumn->PrevCellInColumn = newCell;
                }
            }   // if(currFirstCell->firstCell->row()>row) ends
        }
        else if (count<column)
        {
            currFirstCell->nextIndex = new spreadSheet::cellsStruct::firstCellList;
            currFirstCell->nextIndex->prevIndex = currFirstCell;
            currFirstCell->nextIndex->firstCell = newCell;
        }
        else
        {
            // count > column
            newFirstCell = new spreadSheet::cellsStruct::firstCellList;
            newFirstCell->firstCell = newCell;
            if(currFirstCell->prevIndex)
            {
                newFirstCell->prevIndex = currFirstCell->prevIndex;
                newFirstCell->prevIndex->nextIndex = newFirstCell;
            }
            newFirstCell->nextIndex = currFirstCell;
            currFirstCell->prevIndex = newFirstCell;
        }   // if(count==column) ends
    }   // if(!this->rowFirstsIndex1) to check if some cells exist in the first Cells list
    return newCell;
}

int spreadSheet::WXIDGrid()
{
    return this->wxIDGrid;
}

int spreadSheet::WXIDPanel()
{
    return this->wxIDPanel;
}

bool spreadSheet::evaluate = true;

spreadSheet::spreadSheet()
{
    this->Cells = new spreadSheet::cellsStruct(this);
}

spreadSheet::~spreadSheet()
{
    if(this->SheetGrid)
    {
        delete this->SheetGrid;
        delete this->SheetPanel;
    }
    delete this->Cells;
}

wxGrid *spreadSheet::sheetGrid()
{
    return this->SheetGrid;
}

wxPanel *spreadSheet::sheetPanel()
{
    return this->SheetPanel;
}

std::string spreadSheet::sheetName()
{
    return this->SheetName;
}

std::string spreadSheet::sheetID()
{
    return this->SheetID;
}

shtSize spreadSheet::sheetSize()
{
    return this->SheetSize;
}

bool spreadSheet::setSheetName(std::string name)
{
    this->SheetName = name;
    return true;
}

class spreadSheet *spreadSheetList::AddSheet(wxGrid *sheetGrid, wxPanel *sheetPanel, \
                int wxIDPanel, int wxIDGrid, shtSize *SheetSize, std::string Name)
{
    class spreadSheet *newSheet;
    class spSheetID *newSheetID;
    newSheet = this->AddItem();
    newSheet->wxIDPanel = wxIDPanel;
    newSheet->wxIDGrid = wxIDGrid;
    newSheet->SheetName = Name;
    newSheet->SheetSize.rows = SheetSize->rows;
    newSheet->SheetSize.columns = SheetSize->columns;
    newSheet->SheetGrid = sheetGrid;
    newSheet->SheetPanel = sheetPanel;
    newSheetID = this->idList->AddItem();
    newSheet->SheetID = newSheetID->sheetID;
    newSheetID->spSheet = newSheet;
    return newSheet;
}

class spreadSheet *spreadSheetList::getSheetbyName(std::string Name)
{
    listManager<spreadSheet>::structListItem *tempSheetList;
    tempSheetList = this->firstStructList;
    while(tempSheetList)
    {
        if(tempSheetList->currItem->SheetName == Name)
        {
            return tempSheetList->currItem;
            break;
        }
        tempSheetList = tempSheetList->nextStruct;
    }
    return 0;
}

class spreadSheet *spreadSheetList::getSheetbywxIDPanel(int wxIDVal)
{
    listManager<spreadSheet>::structListItem *tempSheetList;
    tempSheetList = this->firstStructList;
    while(tempSheetList)
    {
        if(tempSheetList->currItem->wxIDPanel == wxIDVal)
        {
            return tempSheetList->currItem;
            break;
        }
        tempSheetList = tempSheetList->nextStruct;
    }
    return 0;
}

class spreadSheet *spreadSheetList::getSheetbywxIDGrid(int wxIDVal)
{
    listManager<spreadSheet>::structListItem *tempSheetList;
    tempSheetList = this->firstStructList;
    while(tempSheetList)
    {
        if(tempSheetList->currItem->wxIDGrid == wxIDVal)
        {
            return tempSheetList->currItem;
            break;
        }
        tempSheetList = tempSheetList->nextStruct;
    }
    return 0;
}

spreadSheetList::spreadSheetList()
{
    // Create an ID List manager
    this->idList = new spSheetIDList(this);
}

spSheetIDList::spSheetIDList(spreadSheetList *spSheets)
{
    this->spSheets = spSheets;
}

spSheetID *spSheetIDList::AddItem()
{
    this->itemCount = this->itemCount + 1;
    if(this->lastStructList == 0)
    {
        this->lastStructList = new structListItem;
        this->lastStructList->currItem = new spSheetID;
        this->firstStructList = this->lastStructList;
        this->lastStructList->currItem->sheetID = this->getUniqueID();
        return this->firstStructList->currItem;
    }
    else
    {
        structListItem *tempStructListItem;
        tempStructListItem = new structListItem;
        tempStructListItem->currItem = new spSheetID;
        this->lastStructList->nextStruct = tempStructListItem;
        tempStructListItem->prevStruct = this->lastStructList;
        this->lastStructList = tempStructListItem;
        this->lastStructList->currItem->sheetID = this->getUniqueID();
        return tempStructListItem->currItem;
    }
}

// Function to generate a new unique ID for a spreadsheet by looking up all previous sheetIDs
std::string spSheetIDList::getUniqueID()
{
    int genID=1;      // Generated ID
    spreadSheet *spSheet;
    listIterator<spreadSheet> *spIter;
    spIter = this->spSheets->getIterator();
    spSheet = spIter->nextStructListItem();
    while(spSheet)
    {
        if (genID == str2int(spSheet->sheetID()))
        {
            genID++;
            spIter->pointToStart();
        }
        spSheet = spIter->nextStructListItem();
    }

    // Now genID contains a unique ID
    return toString(genID);
}

